<template>
  <div class="chat-container">
    <div class="sidebar" :class="{ 'sidebar-expanded': isSidebarExpanded }">
      <div class="sidebar-header">
        <button class="menu-toggle" @click="toggleSidebar">
          <div class="blue-menu-icon">{{ isSidebarExpanded ? '←' : '≡' }}</div>
        </button>
        <div class="sidebar-title" v-if="isSidebarExpanded">菜单</div>
      </div>
      <div class="sidebar-menu">
        <div class="menu-item active" @click="navigateTo('/pages/chat/chat')"><text class="icon">💬</text><span
            v-if="isSidebarExpanded">聊天</span></div>
        <div class="menu-item" @click="checkLoginAndNavigate('/pages/history/history')"><text class="icon">⌚</text><span
            v-if="isSidebarExpanded">历史记录</span></div>
        <div class="menu-item" @click="checkLoginAndNavigate('/pages/star/star')"><text class="icon">★</text><span
            v-if="isSidebarExpanded">收藏</span></div>
        <div class="menu-item" @click="checkLoginAndNavigate('/pages/document/document', true)"><text
            class="icon">📄</text><span v-if="isSidebarExpanded">文档</span></div>
        <div class="menu-item" @click="handleSettingsClick"><text class="icon">⚙️</text><span
            v-if="isSidebarExpanded">设置</span></div>
        <div v-if="showSettingsMenu && isSidebarExpanded" class="settings-dropdown">
          <div class="dropdown-item" @click="checkLoginAndNavigate('/pages/profile/profile')"><text
              class="icon">👤</text><span>个人中心</span></div>
          <div class="dropdown-item" @click="logout"><text class="icon">🚪</text><span>退出登录</span></div>
        </div>
        <!-- 当是管理员的时候，显示切换至后台管理 -->
        <div class="menu-item" @click="switchAdminClick" v-if="isAdmin()">
          <text class="icon">🖥️</text>
          <span v-if="isSidebarExpanded">切换至后台管理</span>
        </div>
      </div>
    </div>
    <div class="sidebar-mask" v-if="isSidebarExpanded && windowWidth <= 768" @click="toggleSidebar"></div>
    <div class="chat-main" :class="{ 'sidebar-open': isSidebarExpanded && windowWidth <= 768 }">
      <div class="top-bar">
        <button class="mobile-menu-btn" @click="toggleSidebar" v-if="windowWidth <= 768 && !isSidebarExpanded">
          <div class="blue-menu-icon">≡</div>
        </button>
        <div class="chat-title">聊天</div>
        <div class="top-actions">
          <div class="user-avatar"><img :src="userAvatarUrl" alt="用户头像" class="user-avatar-img" /></div>
        </div>
      </div>
  <div class="chat-content" ref="chatContent" @scroll="onChatScroll" :class="{ scrollable: isScrollable }">
        <div v-for="(msg, index) in messages" :key="index"
          :class="['message', msg.isUser ? 'user-message' : 'bot-message']">
          <div class="message-avatar" :class="msg.isUser ? '' : 'bot-avatar'">
            <div v-if="!msg.isUser" class="blue-square"></div>
            <img v-if="msg.isUser" :src="userAvatarUrl" alt="用户头像" class="user-avatar-img" />
          </div>
          <div class="message-bubble" :class="msg.isUser ? 'user-bubble' : 'bot-bubble'">
            <div v-if="msg.isUser" class="message-text">{{ msg.content }}</div>
            <div v-else class="message-text">
              <div v-if="streaming && index === currentAnswerIndex && !msg.content" class="typing-indicator"><span
                  class="dot"></span><span class="dot"></span><span class="dot"></span></div>
              <div v-else class="markdown-body" v-html="msg.rendered || safePlainText(msg.content)"></div>
            </div>
          </div>
        </div>
        <div v-if="isLoading && currentAnswerIndex === -1" class="message bot-message">
          <div class="message-avatar bot-avatar">
            <div class="blue-square"></div>
          </div>
          <div class="message-bubble bot-bubble">
            <div class="message-text">
              <div class="typing-indicator"><span class="dot"></span><span class="dot"></span><span class="dot"></span>
              </div>
            </div>
          </div>
        </div>
  </div>
      <div class="input-area in-flow" ref="inputArea">
          <div class="input-wrapper">
            <textarea v-model="message" placeholder="当前问题..." class="message-input" @keyup.enter.prevent="sendMessage"
              :disabled="isLoading || streaming"></textarea>
            <div class="input-actions">
              <button class="action-icon-btn"><text class="icon">🔗</text></button>
             <button class="send-btn" @click="sendMessage" :disabled="isLoading || streaming"><text class="icon">→</text></button>
            </div>
          </div>
          <div style="margin-top:8px;display:flex;align-items:center;gap:8px;">
           <button
            :class="['allow-direct-btn', allowDirectAnswer ? 'active' : '']"
            @click="allowDirectAnswer = !allowDirectAnswer"
            type="button"
          >
            {{ allowDirectAnswer ? '允许AI直接回答' : '仅知识库回答' }}
          </button>
        </div>
        </div>
        <div ref="bottomAnchor" style="height:0;overflow:hidden;padding:0;margin:0;border:0;"></div>
      </div>
      <button v-if="lastAnswerSources.length" class="sources-panel-toggle"
        @click="sourcesPanelOpen = !sourcesPanelOpen">{{ sourcesPanelOpen ? '隐藏来源' : '查看来源(' + lastAnswerSources.length
          + ')'
        }}</button>
      <div v-if="sourcesPanelOpen" class="sources-panel">
        <h4>引用来源</h4>
        <div v-for="s in lastAnswerSources" :key="s.idx" class="source-item">
          <div class="source-title">{{ s.idx }}. {{ s.title }}<span v-if="s.score != null" class="source-score">{{
            (s.score * 100).toFixed(1) }}%</span></div>
          <div class="source-snippet">{{ s.snippet }}</div>
        </div>
      </div>
      <button v-if="streaming" class="cancel-btn" @click="cancelStreaming">停止生成</button>
    </div>
</template>

<script>
import { API_BASE_URL, API_ADMIN } from '@/utils/api-base.js';

export default {
  data() {
    return {
      windowWidth: uni.getSystemInfoSync().windowWidth,
      isSidebarExpanded: uni.getSystemInfoSync().windowWidth > 768,
      message: '',
      showSettingsMenu: false,
      userAvatarUrl: 'https://picsum.photos/id/64/40/40',
      messages: [],
      welcomeText: '你好，我是Ant\n\n基于 Ant Design 的 AGI 产品界面解决方案，创造更美好的智能视界~',
      isLoading: false,
      streaming: false,
      currentAnswerIndex: -1,
      socket: null,
      socketConnected: false,
      reconnectTimer: null,
      pendingQuestion: null,
      baseUrl: 'ws://192.168.14.11:5239',
      // baseUrl 初始仅主机，mounted 后再拼接路径，避免因 mounted 抛错导致使用错误 URL
      lastAnswerSources: [],
      sourcesPanelOpen: false,
      heartbeatTimer: null,
      lastPongAt: 0,
      heartbeatIntervalMs: 25000,
      userAutoScroll: true,
      currentSessionId: null,
      isScrollable: false, // 聊天内容区是否溢出
      allowDirectAnswer: false // 允许AI直接回答
    };
  },
  methods: {
    isAdmin() {
      try {
        const u = uni.getStorageSync('userInfo');        
        return (u.role === '管理员' || u.roleTypes === 'admin' || u.isAdmin === true);
      } catch { return false; }
    },
    switchAdminClick() {
      const userInfo = uni.getStorageSync('userInfo');
      const encodedData = encodeURIComponent(JSON.stringify({
        token: uni.getStorageSync('token'),
        userInfo: userInfo
      }));
      console.log(encodedData);
      // 建议用 uni.navigateTo 跳转小程序内页面，用 window.location.href 跳转外部后台
      // 如需跳转到 web 后台管理页面：
      if (typeof window !== 'undefined') {
        window.location.href = `${API_ADMIN}/admin-redirect?data=${encodedData}`;
      } else {
        // #ifdef MP-WEIXIN
        uni.setClipboardData({ data: `${API_ADMIN}/admin-redirect?data=${encodedData}` });
        uni.showToast({ title: '链接已复制，请在浏览器打开后台', icon: 'none' });
        // #endif
      }
    },
    formatTime(date) { return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`; },
    onChatScroll() {
      const el = this.$refs.chatContent; if (!el) return;
      const distanceFromBottom = el.scrollHeight - (el.scrollTop + el.clientHeight);
      // 计算输入框顶部作为“底”参考：因为 chat-content bottom 已减掉输入区高度，此处 distanceFromBottom 已经是与锚点距离
      // 动态阈值：min(140, 0.25 * 可视区高度)
      const dyn = Math.min(140, el.clientHeight * 0.25);
      this.userAutoScroll = distanceFromBottom < dyn;
      // 检查内容是否溢出，决定是否显示滚动条
      this.isScrollable = el.scrollHeight > el.clientHeight + 2;
    },
    scrollToBottom(force = false) {
      this.$nextTick(() => {
        const wrap = this.$refs.chatContent;
        if (!wrap) return;
        // 检查内容是否溢出，决定是否显示滚动条
        this.isScrollable = wrap.scrollHeight > wrap.clientHeight + 2;
        if (!force && !this.userAutoScroll) return; // 用户上翻则不打扰
        const anchor = this.$refs.bottomAnchor;
        const target = anchor ? (anchor.offsetTop - 10) : wrap.scrollHeight;
        wrap.scrollTop = target;
        setTimeout(() => { if (wrap) wrap.scrollTop = target; }, 30);
        if (typeof requestAnimationFrame === 'function') requestAnimationFrame(() => { if (wrap) wrap.scrollTop = target; });
        // #ifdef MP-WEIXIN
        setTimeout(() => {
          uni.pageScrollTo({
            scrollTop: 99999,
            duration: 100
          });
        }, 50);
        // #endif
      });
    },
  mounted() {
    // 初始检查内容是否溢出
    this.$nextTick(() => {
      const wrap = this.$refs.chatContent;
      if (wrap) this.isScrollable = wrap.scrollHeight > wrap.clientHeight;
    });
  },
    // 移除自适应高度逻辑：输入区随内容滚动
    saveToHistory(question, answer) { const list = uni.getStorageSync('historyList') || []; list.push({ id: Date.now(), question, answer, content: question, time: this.formatTime(new Date()) }); if (list.length > 100) list.shift(); uni.setStorageSync('historyList', list); },
    checkLogin() { const t = uni.getStorageSync('token'); const u = uni.getStorageSync('userInfo'); return !!t && !!u; },
    isGuest() { const u = uni.getStorageSync('userInfo'); return u && u.roleType === 'guest'; },
    handleSocketMessage(msg) {
      if (msg.type === 'ack') { this.streaming = true; this.isLoading = true; return; }
      if (msg.type === 'sources') { this.lastAnswerSources = (msg.sources || []).map((s, i) => ({ idx: i + 1, title: s.title || s.name || ('文档' + (i + 1)), score: typeof s.score === 'number' ? s.score : undefined, snippet: (s.content || s.text || '').slice(0, 180) })); return; }
      if (msg.type === 'answer_chunk') {
        if (this.currentAnswerIndex === -1) {
          this.messages.push({ isUser: false, content: '', rendered: '' });
          this.currentAnswerIndex = this.messages.length - 1;
        }
        this.messages[this.currentAnswerIndex].content += msg.content;
        // 增量解析 markdown（简单解析，可能在未闭合代码块时样式暂不稳定）
        this.messages[this.currentAnswerIndex].rendered = this.parseMarkdown(this.messages[this.currentAnswerIndex].content);
        if (this.isLoading) this.isLoading = false; // 首个 chunk 到达后隐藏 loading 气泡
  this.scrollToBottom(); // 每个增量都滚动
        return;
      }
      if (msg.type === 'answer_end') {
        if (this.currentAnswerIndex !== -1) {
          const raw = this.messages[this.currentAnswerIndex].content;
          this.messages[this.currentAnswerIndex].rendered = this.parseMarkdown(raw);
        }
        this.streaming = false; this.isLoading = false; this.currentAnswerIndex = -1;
        this.$nextTick(() => this.scrollToBottom(true));
        const lastUser = [...this.messages].reverse().find(m => m.isUser); const lastAi = [...this.messages].reverse().find(m => !m.isUser); if (lastUser && lastAi) this.saveToHistory(lastUser.content, lastAi.content); return;
      }
      if (msg.type === 'error') { this.streaming = false; this.isLoading = false; this.currentAnswerIndex = -1; this.messages.push({ isUser: false, content: '【错误】' + (msg.message || '未知错误') }); return; }
      if (msg.type === 'pong') { this.lastPongAt = Date.now(); return; }
    },
    escapeHtml(str) { if (!str) return ''; return str.replace(/[&<>"']/g, s => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', '\'': '&#39;' }[s])); },
    safePlainText(str) { return this.escapeHtml(str); },
    parseMarkdown(src) {
      if (!src) return '';
      // 先对 < > & 做转义再做有限 markdown 转换，防止 XSS（允许生成的基本标签）
      let text = this.escapeHtml(src);
      // 代码块 ```lang\n...``` -> <pre><code class="lang">...</code></pre>
      text = text.replace(/```(\w+)?\n([\s\S]*?)```/g, (m, lang, code) => `<pre class="md-code"><code class="lang-${lang || 'plain'}">${code.replace(/</g, '&lt;')}</code></pre>`);
      // 行内代码 `code`
      text = text.replace(/`([^`]+)`/g, (m, c) => `<code class="md-inline">${c}</code>`);
      // 粗体 **text** 或 __text__
      text = text.replace(/(\*\*|__)(.*?)\1/g, (m, _b, inner) => `<strong>${inner}</strong>`);
      // 斜体 *text* 或 _text_
      text = text.replace(/(^|[^*])\*(?!\*)([^*]+)\*(?!\*)/g, (m, pre, inner) => `${pre}<em>${inner}</em>`);
      text = text.replace(/(^|[^_])_(?!_)([^_]+)_(?!_)/g, (m, pre, inner) => `${pre}<em>${inner}</em>`);
      // 标题 ###### 到 #
      text = text.replace(/^######\s+(.*)$/gm, '<h6>$1</h6>')
        .replace(/^#####\s+(.*)$/gm, '<h5>$1</h5>')
        .replace(/^####\s+(.*)$/gm, '<h4>$1</h4>')
        .replace(/^###\s+(.*)$/gm, '<h3>$1</h3>')
        .replace(/^##\s+(.*)$/gm, '<h2>$1</h2>')
        .replace(/^#\s+(.*)$/gm, '<h1>$1</h1>');
      // 无序列表 - * +
      const listRegex = /^(?:[*+-])\s+.*(?:\n(?:[*+-])\s+.*)*/gm;
      text = text.replace(listRegex, block => {
        const items = block.split(/\n/).map(l => l.replace(/^[*+-]\s+/, '')).map(li => `<li>${li}</li>`).join('');
        return `<ul>${items}</ul>`;
      });
      // 有序列表 1. 2.
      const olistRegex = /^(?:\d+\.)\s+.*(?:\n(?:\d+\.)\s+.*)*/gm;
      text = text.replace(olistRegex, block => {
        const items = block.split(/\n/).map(l => l.replace(/^\d+\.\s+/, '')).map(li => `<li>${li}</li>`).join('');
        return `<ol>${items}</ol>`;
      });
      // 链接 [text](url)
      text = text.replace(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
      // 换行（保留段落）
      text = text.replace(/\n{2,}/g, '</p><p>');
      text = '<p>' + text + '</p>';
      return text;
    },
    buildWsUrl() {
      const wsBase = uni.getStorageSync('wsBase');
      if (wsBase) { return wsBase.replace(/\/?$/, '') + '/ws/rag'; }
      const apiHost = uni.getStorageSync('apiHost');
      // #ifdef H5
      try {
        const loc = window.location;
        const proto = loc.protocol === 'https:' ? 'wss' : 'ws';
        // 开发 H5 场景：前端端口 5173，后端默认 5239，需要替换端口
        const backendPort = 5239;
        // 如果用户已经设置 apiHost 则不走这里
        if (!apiHost) {
          return `${proto}://${loc.hostname}:${backendPort}/ws/rag`;
        }
      } catch (e) { }
      // #endif
      if (apiHost) { return 'ws://' + apiHost.replace(/^ws(s)?:\/\//, '') + '/ws/rag'; }
      return `ws://${API_BASE_URL}/ws/rag`;
    },
    startHeartbeat() { if (this.heartbeatTimer) return; this.lastPongAt = Date.now(); this.heartbeatTimer = setInterval(() => { if (!this.socketConnected || !this.socket) return; try { this.socket.send({ data: JSON.stringify({ type: 'ping' }) }); } catch { } if (Date.now() - this.lastPongAt > this.heartbeatIntervalMs * 2) { try { this.socket.close(); } catch { } } }, this.heartbeatIntervalMs); },
    stopHeartbeat() { if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = null; } },
    initSocket() {
      if (this.socketConnected) return;
      const url = this.baseUrl;
      console.log('[WS] connecting', url);
      this.socket = uni.connectSocket({ url, success() { }, fail: e => { console.error('WS连接失败', e); } });
      this.socket.onOpen(() => { console.log('[WS] open'); this.socketConnected = true; this.streaming = false; if (this.pendingQuestion) { const q = this.pendingQuestion; this.pendingQuestion = null; this.sendQuestion(q); } });
      this.socket.onMessage(res => {
        try {
          const obj = JSON.parse(res.data); // console.debug('[WS] msg', obj); 
          this.handleSocketMessage(obj);
        } catch (err) { console.warn('WS消息解析失败', res.data); }
      });
      this.socket.onClose(ev => { console.log('[WS] close', ev); this.socketConnected = false; this.streaming = false; this.tryReconnect(); });
      this.socket.onError(err => { console.error('[WS] error', err); this.socketConnected = false; this.streaming = false; this.tryReconnect(); });
    },
    tryReconnect() { if (this.reconnectTimer) return; this.reconnectTimer = setTimeout(() => { this.reconnectTimer = null; this.initSocket(); }, 1500); },
    sendQuestion(q) { 
      if (!this.socketConnected) { 
        this.pendingQuestion = q; 
        this.initSocket(); 
        return; 
      } 
      try { 
        // 获取用户信息
        const userInfo = uni.getStorageSync('userInfo');
        const userId = userInfo && userInfo.id ? userInfo.id : '00000000-0000-0000-0000-000000000001';
        const sessionId = this.getCurrentSessionId();
        
        this.socket.send({ 
          data: JSON.stringify({ 
            type: 'question', 
            question: q,
            userId: userId,
            sessionId: sessionId,
            allowDirectAnswer: this.allowDirectAnswer
          }) 
        }); 
      } catch (e) { 
        console.error('发送失败', e); 
      } 
    },
    sendMessage() { const c = this.message.trim(); if (!c || this.streaming) return; this.lastAnswerSources = []; this.sourcesPanelOpen = false; this.messages.push({ isUser: true, content: c }); this.message = ''; this.scrollToBottom(); this.isLoading = true; this.sendQuestion(c); },
    cancelStreaming() { if (!this.streaming) return; try { if (this.socketConnected && this.socket) { this.socket.send({ data: JSON.stringify({ type: 'cancel' }) }); } } catch { } this.streaming = false; this.isLoading = false; this.currentAnswerIndex = -1; },
    toggleSidebar() { this.isSidebarExpanded = !this.isSidebarExpanded; },
    navigateTo(url) { if (this.windowWidth <= 768) this.isSidebarExpanded = false; uni.navigateTo({ url }); },
    onResize() { this.windowWidth = uni.getSystemInfoSync().windowWidth; this.isSidebarExpanded = this.windowWidth > 768; },
    checkLoginAndNavigate(url, allowGuest = false) { if (this.windowWidth <= 768) this.isSidebarExpanded = false; if (!this.checkLogin()) { uni.showModal({ title: '未登录', content: '请先登录后再进行操作', confirmText: '去登录', cancelText: '取消', success: r => { if (r.confirm) uni.navigateTo({ url: '/pages/login/login' }); } }); return; } if (!allowGuest && this.isGuest()) { uni.showModal({ title: '权限不足', content: '该功能仅对注册用户开放，是否前往登录？', confirmText: '去登录', cancelText: '取消', success: r => { if (r.confirm) { uni.clearStorageSync(); uni.navigateTo({ url: '/pages/login/login' }); } } }); return; } uni.navigateTo({ url }); },
    handleSettingsClick() { if (!this.checkLogin()) { uni.showModal({ title: '未登录', content: '请先登录后再进行操作', confirmText: '去登录', cancelText: '取消', success: r => { if (r.confirm) uni.navigateTo({ url: '/pages/login/login' }); } }); return; } if (this.isGuest()) { uni.showModal({ title: '权限不足', content: '该功能仅对注册用户开放，是否前往登录？', confirmText: '去登录', cancelText: '取消', success: r => { if (r.confirm) { uni.clearStorageSync(); uni.navigateTo({ url: '/pages/login/login' }); } } }); return; } this.showSettingsMenu = !this.showSettingsMenu; },
    logout() { uni.showModal({ title: '确认退出', content: '确定要退出登录吗？', confirmText: '确定', cancelText: '取消', success: r => { if (r.confirm) { uni.clearStorageSync(); this.showSettingsMenu = false; uni.reLaunch({ url: '/pages/login/login' }); } } }); },
    getCurrentSessionId() {
      // 如果没有当前会话ID，创建一个新的
      if (!this.currentSessionId) {
        this.currentSessionId = this.generateGuid();
      }
      return this.currentSessionId;
    },
    generateGuid() {
      return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        const r = Math.random() * 16 | 0;
        const v = c == 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(16);
      });
    }
  },
  watch: { windowWidth(nv) { if (nv > 768) this.isSidebarExpanded = true; } },
  mounted() {
    // 初始化窗口尺寸
    const systemInfo = uni.getSystemInfoSync();
    this.windowWidth = systemInfo.windowWidth;
    this.windowHeight = systemInfo.windowHeight;

    if (typeof this.onResize === 'function') {
      this.onResize();
    }

    this.isSidebarExpanded = this.windowWidth > 768;
    const info = uni.getStorageSync('userInfo');
    if (info && info.avatarUrl) this.userAvatarUrl = info.avatarUrl;

    // 自动滚动到底部（初始加载）
    this.$nextTick(() => {
      this.scrollToBottom(true);
    });

    // 监听消息数组变化，自动滚动
    this.$watch(
      () => this.messages.length,
      () => {
        this.$nextTick(() => {
          this.scrollToBottom(true);
        });
      }
    );

    // 安全注册窗口尺寸监听（不同平台 API 支持不一致）
    try {
      if (typeof uni.onWindowResize === 'function') {
        uni.onWindowResize(this.onResize);
      } else if (typeof window !== 'undefined') {
        window.addEventListener('resize', this.onResize);
      }
    } catch (e) {
      // 忽略，继续执行后续逻辑
      console.warn('register resize listener failed', e);
    }
    this.baseUrl = this.buildWsUrl();
    this.initSocket();
    this.startHeartbeat();
    this.$nextTick(() => { const el = this.$refs.chatContent; if (el && el.addEventListener) el.addEventListener('scroll', this.onChatScroll, { passive: true }); });
  },
  created() {
    // 初始化欢迎消息，避免模板中重复出现静态块
    this.messages.push({ isUser: false, content: this.welcomeText, rendered: this.parseMarkdown ? this.parseMarkdown(this.welcomeText) : this.welcomeText });
  },
  beforeDestroy() {
    this.stopHeartbeat();
    try {
      if (typeof uni.offWindowResize === 'function') {
        uni.offWindowResize(this.onResize);
      } else if (typeof window !== 'undefined') {
        window.removeEventListener('resize', this.onResize);
      }
    } catch { }
    if (this.socket) { try { this.socket.close(); } catch { } }
    try { const el = this.$refs.chatContent; if (el && el.removeEventListener) el.removeEventListener('scroll', this.onChatScroll); } catch { }
  }
};
</script>

<style scoped>
/* 聊天内容区滚动和高度适配 */
 .chat-content {
  overflow-y: auto;
  height: calc(100vh - 120px);
  /* 120px 预留顶部栏和输入区，可根据实际调整 */
  background: #f7f8fa;
  /* padding-bottom: 20px; 移除，避免与下方120px冲突 */
}
div,
button,
input,
text,
span,
img,
html,
body,
textarea {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html,
body {
  width: 100%;
  height: 100%;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

.chat-container {
  display: flex;
  width: 100vw;
  height: 100vh;
  position: relative;
  overflow: hidden;
}

/* 全屏背景样式 */
.top-bg {
  background: linear-gradient(135deg, #f9f9f9 0%, #b3d9ff 100%);
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 0;
}

/* 侧边栏样式 */
.sidebar {
  width: 50px;
  height: 100%;
  background-color: white;
  transition: width 0.3s ease, transform 0.3s ease;
  box-shadow: 1px 0 3px rgba(0, 0, 0, 0.1);
  display: flex;
  flex-direction: column;
  z-index: 10;
}

.sidebar-expanded {
  width: 200px;
}

.sidebar-header {
  display: flex;
  align-items: center;
  padding: 12px;
  border-bottom: 1px solid #f0f0f0;
  width: 100%;
  box-sizing: border-box;
  justify-content: center;
}

.blue-menu-icon {
  color: white;
  background-color: #1890ff;
  width: 30px;
  height: 30px;
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: bold;
  font-size: 18px;
  overflow: visible;
  box-sizing: border-box;
}

.menu-toggle {
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
  margin: 0;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.2s;
}

.menu-toggle:hover {
  transform: scale(1.1);
}

.sidebar-title {
  margin-left: 10px;
  font-size: 16px;
  font-weight: 600;
  color: #333;
}

.sidebar-menu {
  margin-top: 20px;
}

.menu-item {
  display: flex;
  align-items: center;
  padding: 12px 15px;
  color: #666;
  cursor: pointer;
  transition: all 0.2s;
}

.menu-item:hover {
  background-color: #f5f5f5;
  padding-left: 18px;
}

.menu-item.active {
  background-color: #e6f7ff;
  color: #1890ff;
  border-left: 3px solid #1890ff;
}

.icon {
  font-size: 18px;
  width: 20px;
  text-align: center;
}

.menu-item span {
  margin-left: 15px;
}

.sidebar-mask {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.3);
  z-index: 9;
  transition: opacity 0.3s;
}

/* 主聊天区域 */
.chat-main {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  transition: transform 0.3s ease;
  z-index: 1;
}

/* 顶部导航栏 - 固定顶部 */
.top-bar {
  height: 50px;
  display: flex;
  align-items: center;
  padding: 0 15px;
  background-color: white;
  border-bottom: 1px solid #f0f0f0;
  z-index: 5;
  position: fixed;
  top: 0;
  right: 0;
  left: 50px;
  transition: left 0.3s ease;
}

.sidebar-expanded~.chat-main .top-bar {
  left: 200px;
}

@media (max-width: 768px) {
  .top-bar {
    left: 0;
  }
}

.mobile-menu-btn {
  background: none;
  border: none;
  cursor: pointer;
  margin-right: 10px;
  display: none;
  padding: 5px;
}

.chat-title {
  font-size: 18px;
  font-weight: 600;
  color: #333;
  margin-right: 10px;
}

.top-actions {
  display: flex;
  align-items: center;
  margin-left: auto;
  gap: 10px;
}

.user-avatar {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  overflow: hidden;
}

.user-avatar-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* 聊天内容区域 - 可滚动（输入框内联） */
.chat-main {
  display: flex;
  flex-direction: column;
}

.chat-content {
  flex: 1;
  padding: 65px 15px 0px 15px;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  scroll-behavior: smooth;
  display: flex;
  flex-direction: column;
   padding-bottom: 120px; /* 等于输入框高度 + 额外间距 */
  height: calc(100vh - 50px - 100px);  /* 减去顶部导航栏高度 */
}

/* 消息样式 */
.message {
  display: flex;
  margin-bottom: 20px;
  max-width: 85%;
  align-items: flex-start;
}

.bot-message {
  margin-right: auto;
}

.user-message {
  margin-left: auto;
  flex-direction: row-reverse;
}

.message-avatar {
  width: 40px;
  height: 40px;
  flex-shrink: 0;
}

.user-avatar-margin {
  margin-right: 0px;
}

.bot-avatar {
  margin-right: 10px;
}

.blue-square {
  width: 100%;
  height: 100%;
  background-color: #71b4f3;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.message-bubble {
  word-wrap: break-word;
  word-break: break-all;
  box-sizing: border-box;
  padding: 15px;
  border-radius: 10px;
  position: relative;
  max-width: calc(100% - 50px);
  line-height: 1.5;
  color: rgb(105, 104, 104);
  margin-right: 12px;
}

.bot-bubble {
  background: linear-gradient(135deg, #f6f6cd 0%, #cdc5f7 100%);
}

.user-bubble {
  background: linear-gradient(135deg, #c0defa 0%, #f8f8e6 100%);
}

.bot-message,
.user-message {
  max-width: 85%;
}

.message-text {
  font-size: 15px;
  white-space: normal;
}

/* 切换状态 */
.allow-direct-btn {
  padding: 2px 8px;
  border-radius: 10px;
  border: none;
  background: #eee;
  color: #666;
  font-size: 11px;
  cursor: pointer;
  transition: background 0.2s, color 0.2s;
  min-width: 60px;
  min-height: 24px;
}
.allow-direct-btn.active {
  background: linear-gradient(90deg, #71b4f3 0%, #ffd666 100%);
  color: #222;
  font-weight: bold;
}

/* markdown 渲染样式 */
.markdown-body {
  font-size: 14px;
  line-height: 1.6;
}

.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
  font-weight: 600;
  margin: 10px 0 6px;
}

.markdown-body p {
  margin: 4px 0;
}

.markdown-body ul,
.markdown-body ol {
  padding-left: 20px;
  margin: 6px 0;
}

.markdown-body li {
  margin: 2px 0;
}

.markdown-body code.md-inline {
  background: rgba(255, 255, 255, 0.15);
  padding: 2px 4px;
  border-radius: 4px;
  font-family: monospace;
  font-size: 12px;
}

.markdown-body pre.md-code {
  background: rgba(0, 0, 0, 0.25);
  padding: 10px;
  border-radius: 6px;
  overflow: auto;
  font-size: 12px;
  line-height: 1.5;
}

.markdown-body a {
  color: #ffd666;
  text-decoration: underline;
}

/* 输入区域 - 固定在底部 */
.input-area {
  display: flex;
  align-items: center;
  padding: 12px 15px;
  background-color: white;
  border-top: 1px solid #f0f0f0;
  z-index: 5;
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  max-width: calc(100% - 50px);
  margin-left: 50px;
  transition: margin-left 0.3s ease, left 0.3s ease;
  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
  min-height: 100px;
}

.sidebar-expanded~.chat-main .input-area {
  max-width: calc(100% - 200px);
  margin-left: 200px;
}

.input-wrapper {
  position: relative;
  width: 100%;
  height: 100%;
}

.message-input {
  width: 100%;
  min-height: 60px;
  max-height: 200px;
  padding: 15px 80px 15px 15px;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  font-size: 14px;
  outline: none;
  transition: border-color 0.2s;
  resize: none;
  overflow-y: auto;
  line-height: 1.5;
}

.message-input:focus {
  border-color: #1890ff;
}

.message-input:disabled {
  background-color: #f5f5f5;
  cursor: not-allowed;
}

.input-actions {
  position: absolute;
  right: 10px;
  bottom: 10px;
  display: flex;
  gap: 10px;
}

.action-icon-btn,
.send-btn {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: none;
  font-size: 16px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.action-icon-btn {
  background: none;
  color: #666;
  padding: 0;
  margin: 0;
}

.send-btn {
  background-color: #1890ff;
  color: white;
  transition: background-color 0.2s;
}

.send-btn:disabled {
  background-color: #a0cfff;
  cursor: not-allowed;
}

.send-btn .icon {
  font-weight: bold;
  font-size: 18px;
}

/* 加载动画 */
.typing-indicator {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  height: 20px;
}

.dot {
  width: 8px;
  height: 8px;
  background-color: rgba(255, 255, 255, 0.8);
  border-radius: 50%;
  animation: typing 1.4s infinite ease-in-out both;
}

.dot:nth-child(1) {
  animation-delay: -0.32s;
}

.dot:nth-child(2) {
  animation-delay: -0.16s;
}

/* 切换按钮 */
.allow-direct-btn {
  padding: 6px 16px;
  border-radius: 16px;
  border: none;
  background: #eee;
  color: #666;
  font-size: 13px;
  cursor: pointer;
  transition: background 0.2s, color 0.2s;
}
.allow-direct-btn.active {
  background: linear-gradient(90deg, #71b4f3 0%, #ffd666 100%);
  color: #222;
  font-weight: bold;
}

@keyframes typing {

  0%,
  80%,
  100% {
    transform: scale(0);
  }

  40% {
    transform: scale(1);
  }
}

/* 移动端适配 */
@media (max-width: 320px) {

  /* 小屏手机 */
  .input-area {
    padding: 8px;
    min-height: 90px;
  }

  .message-input {
    padding: 10px 60px 10px 10px;
    font-size: 13px;
    min-height: 50px;
  }

  .action-icon-btn,
  .send-btn {
    width: 32px;
    height: 32px;
  }

  .chat-content {
    padding-bottom: 130px !important;
  }
}

@media (min-width: 321px) and (max-width: 375px) {

  /* 普通手机 */
  .input-area {
    padding: 10px;
    min-height: 100px;
  }

  .message-input {
    padding: 12px 65px 12px 12px;
    min-height: 60px;
  }

  .chat-content {
    padding-bottom: 140px !important;
  }
}

@media (min-width: 376px) and (max-width: 430px) {

  /* 大屏手机 */
  .input-area {
    padding: 12px;
    min-height: 110px;
  }

  .message-input {
    padding: 14px 75px 14px 14px;
    font-size: 15px;
    min-height: 70px;
  }

  .action-icon-btn,
  .send-btn {
    width: 38px;
    height: 38px;
  }

  .chat-content {
    padding-bottom: 150px !important;
  }
}

@media (max-width: 768px) {
  .sidebar {
    position: absolute;
    transform: translateX(-100%);
  }

  .sidebar-expanded {
    transform: translateX(0);
  }

  .chat-main.sidebar-open {
    transform: translateX(200px);
  }

  .mobile-menu-btn {
    display: block;
  }

  /* 保留足够底部空间避免被固定输入框遮挡 */
  .chat-content {
    padding: 10px 10px 190px 10px;
    padding-top: 65px;
  }

  .message {
    max-width: 90%;
  }

  .message-text {
    font-size: 14px;
  }

  .input-area {
    max-width: 100%;
    margin: 0;
    left: 0;
  }

  .message-input {
    padding: 12px 70px 12px 12px;
    font-size: 14px;
  }

  .action-icon-btn,
  .send-btn {
    width: 34px;
    height: 34px;
  }

  .send-btn .icon {
    font-size: 16px;
  }
}

/* 平板及以上设备适配 */
@media (min-width: 769px) {
  .sidebar-mask {
    display: none;
  }

  .mobile-menu-btn {
    display: none !important;
  }
}

/* 设置下拉菜单样式 */
.settings-dropdown {
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  margin: 8px 0 0 0;
  padding: 4px 0;
  position: relative;
  z-index: 20;
}

.dropdown-item {
  display: flex;
  align-items: center;
  padding: 10px 30px 10px 40px;
  cursor: pointer;
  color: #333;
  font-size: 15px;
  transition: background 0.2s;
}

.dropdown-item:hover {
  background: #f5f5f5;
}

.dropdown-item .icon {
  margin-right: 12px;
  font-size: 18px;
}

/* 来源面板 & 取消按钮 */
.sources-panel-toggle {
  position: fixed;
  right: 10px;
  bottom: 140px;
  background: #fff;
  border: 1px solid #e0e0e0;
  padding: 6px 10px;
  border-radius: 18px;
  font-size: 12px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}

.sources-panel {
  position: fixed;
  right: 10px;
  bottom: 190px;
  width: 280px;
  max-height: 45vh;
  overflow: auto;
  background: #fff;
  border: 1px solid #e0e0e0;
  border-radius: 12px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
  padding: 12px;
}

.sources-panel h4 {
  margin: 0 0 8px 0;
  font-size: 14px;
  font-weight: 600;
}

.source-item {
  padding: 8px 6px;
  border-radius: 6px;
  background: #fafafa;
  margin-bottom: 6px;
}

.source-title {
  font-size: 13px;
  font-weight: 600;
  color: #333;
}

.source-score {
  font-size: 11px;
  color: #999;
  margin-left: 4px;
}

.source-snippet {
  font-size: 12px;
  margin-top: 4px;
  color: #555;
  line-height: 1.45;
}

.cancel-btn {
  position: fixed;
  right: 10px;
  bottom: 90px;
  background: #ff7875;
  color: #fff;
  border: none;
  padding: 8px 14px;
  border-radius: 22px;
  font-size: 12px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
}
/* 聊天内容区仅溢出时显示滚动条 */
.chat-content {
  overflow-y: hidden;
}
.chat-content.scrollable {
  overflow-y: auto;
}
</style>
